home *** CD-ROM | disk | FTP | other *** search
- INTRODUCCION AL ASM: LA PILA
- ============================
-
- Como comentábamos al final del cuarto capítulo, en este quinto capítulo
- vamos a estudiar uno de los elementos de mayor utilidad del ASM: la pila.
- Al estudiarla veremos dos nuevos registros, el SS y el SP, y dos nuevas
- instrucciones, PUSH y POP.
-
- Primero explicaré lo que se entiende por una pila en general y después vere-
- mos la pila del 8086 en concreto. Por eso, si alguien sabe lo que es una pila,
- puede saltarse algunas líneas.
-
- Un ordenador siempre guarda los datos en la memoria, exceptuando los regis-
- tros de la CPU. Estos datos pueden estar dispuestos de diferentes maneras, y se
- puede acceder a ellos de diversas formas. Una particular organización y modo de
- acceso a los datos se suele denominar 'estructura de datos', de las que existen
- muchas y muy diferentes, cada una adecuada para un propósito. Son estructuras
- de datos los arrays, las listas encadenadas, los árboles binarios, etc...
-
- Las estructuras que nos interesan ahora aquí son las estructuras lineales,
- en que cada elemento (cada dato) va después del anterior. Es decir, cada ele-
- mento tiene un predecesor y un sucesor, excepto el primero y el último. Son
- estructuras de este tipo los arrays y las listas encadenadas, mientras que no
- lo son los árboles binarios (no os preocupéis los que no sepáis que son estas
- cosillas). Las estructuras lineales se pueden clasificar principalmente en dos
- grupos: las llamadas FIFO (First In First Out, el primero en entrar es el pri-
- mero en salir) y las llamadas LIFO (Last In First Out, el último en entrar es
- el primero en salir). Aquí van dos ejemplos de cada una para que quede claro:
-
- Una estructura FIFO es similar a la cola del autobús: los elementos se orde-
- nan según llegan (los pasajeros se ponen a la cola cada uno detrás del anterior
- en llegar) y se sacan empezando por el primero que llegó y acabando por el úl-
- timo (el primero en llegar a la parada es el primero en montar). Esta estructu-
- ra se suele denominar 'cola' o 'queue' ('cola' en inglés).
-
- En cambio, una estructura LIFO es similar al montón de papeles que suelen
- estar pinchados todos juntos en una tintorería: los elementos se van ordenando
- según llegan (cada papel se pincha sobre el anterior), pero al sacarse del
- montón se sacan empezando por el último que llegó (se empiezan a sacar por el
- último que se pinchó, justo en orden inverso al que se pincharon). Esta estruc-
- tura se suele denominar 'pila' o 'stack' ('pila' en inglés).
-
- Estas estructuras se pueden implementar de muchas maneras diferentes por un
- programa (todavía no estamos viendo la pila del 8086 en concreto, ésta viene ya
- implementada por el hard del uP). Una manera sencilla de hacerlo es un array
- del tipo de dato que queremos cada elemento: si queremos una cola de enteros
- será un array de enteros, si queremos una pila de números reales será un array
- de números en coma flotante, etc... Necesitaremos también una variable entera
- que llevará la cuenta de los elementos insertados hasta el momento, que llama-
- remos 'cuenta'. Veamos cómo se implementaría una pila con este esquema:
- inicialmente, la variable 'cuenta' valdría -1 (suponemos que los índices de los
- arrays van como en C, comenzando por el cero). Para insertar un elemento, se
- incrementaría 'cuenta' y se guardaría el elemento en la posición número 'cuen-
- ta' del array. De esta manera, siempre tendríamos accesible el último valor
- empujado en 'pila[cuenta]'. Una jugada inteligente sería meter en este punto
- una comprobación de que 'cuenta' no ha sobrepasado el límite superior de la
- pila. En caso de que así fuera, emitiríamos un mensaje de error. Este error se
- suele denominar 'stack overflow' o 'desbordamiento de la pila', y aunque en la
- pila del 8086 no se comprueba internamente, los compiladores de lenguajes de
- alto nivel suelen incluir código para comprobarlo. A esta operación de intro-
- ducir un valor en la pila se le suele llamar 'empujar' un valor en la pila (en
- inglés 'push').
-
- La otra operación que nos queda ver es la operación contraria, la de sacar
- un valor de la pila. Ya que queremos acceder a la estructura en la forma LIFO,
- cada vez que se extrae un valor debe ser del extremo superior de la pila. Por
- ejemplo, si 'cuenta' vale 4 y queremos extraer un valor, éste deberá tomarse de
- la posición 4 del array, que será el último elemento empujado. Además, hay que
- decrementar la variable 'cuenta' para apuntar al nuevo 'tope' de la pila. Es
- importante tener en cuenta que no hace falta sobreescribir la posición 4 del
- array, esto ocurrirá la próxima vez que se empuje un valor, sino que basta con
- decrementar la variable 'cuenta'. A esta otra operación se le llama 'pop' en
- inglés, en español se suele decir simplemente 'sacar' o 'extraer' un valor de
- la pila.
-
- La pila del 8086 viene implementada por el hard del uP, pero los datos resi-
- den en memoria. El uP tiene dos registros para manejar la pila: el SS ('Stack
- Segment', 'segmento de pila'), que es el cuarto registro de segmento que junto
- con CS, DS y ES se utilizan para formar direcciones de 20 bits, y el SP ('Stack
- Pointer', 'puntero de pila'), que es un registro de 16 bits que cumple la
- función de nuestra variable 'cuenta' y que se une al SP para formar la direc-
- ción de memoria completa. Cada programa suele destinar una zona de la memoria
- para la pila, y es esta zona la que alberga los valores introducidos en ésta.
- Todos los elementos de la pila son valores enteros de 16 bits, por lo que se
- utiliza una palabra de la memoria para cada valor.
-
- Un aspecto que en un principio puede llamar la atención es que la pila del
- 8086, al igual que la de todos los micros que conozco, crece hacia abajo en
- lugar de hacia arriba. El funcionamiento de ésta es, por lo demás, idéntico al
- de nuestra pila imaginaria: al principio 'cuenta' apuntaría al último elemento
- del array más uno, al empujar un valor se se decrementaría cuenta y metería en
- 'pila[cuenta]' , y al sacarlo se incrementaría cuenta y se devolvería
- 'pila[cuenta]'. Más adelante se verán las ventajas de que la pila crezca hacia
- abajo.
-
- Los registros SS y SP forman la dirección absoluta donde reside el último
- valor empujado. Al empujar un valor, se resta dos a SP (porque cada elemento
- son dos bytes, es decir, dos posiciones de memoria) y el nuevo valor se guarda
- en la dir SS:SP (y el byte de mayor peso en SS:SP+1). Al sacar un valor, se
- toma de las posiciones SS:SP y SS:SP+1 y se suma dos a SP.
-
- Las instrucciones PUSH y POP nos permiten empujar un valor de 16 bits y
- recoger el último valor empujado. El uP se encarga de actualizar SP.
-
- Vamos a ver un ejemplo un poco 'incivilizado' de cómo pondríamos a punto la
- pila en una dirección determinada. Digo 'incivilizado' porque lo hacemos en una
- dirección arbitraria, donde en un PC podría haber un driver o cualquier cosa.
- De esto normalmente se encarga la rutina del DOS que carga un programa y lo
- ejecuta, por lo que sólo es un ejemplo para comprender el funcionamiento.
-
- Como sagazmente habréis deducido (y por si acaso no, os lo cuento), ya que
- la gestión de la pila se hace modificando sólo el SP al hacer PUSH y POP, la
- pila no puede exceder de 64K. Supongamos que queremos poner una pila de 64K en
- el segmento que comienza en la dirección absoluta 80000h (8000h:0). La última
- palabra de este segmento está en 8FFFEh (el byte de menor peso en ésta y el de
- mayor en la 8FFFFh). Por tanto, habría que cargar los registros como sigue:
-
- MOV AX,8000h
- MOV SS,AX
- MOV SP,0FFFEh
-
- Más adelante veremos que esto no se puede hacer así, sino que hay que tener
- en cuenta las interrupciones, etc.. Por lo que no lo probéis. Pero para hacerse
- una idea, está bien. En realidad, no es necesario reservar 64K para la pila,
- por lo que SP no tiene por qué inicializarse a 0FFFEh. Por ejemplo, si queremos
- reservar 32K para la pila inicializaremos SP a 7FFEh.
-
- El funcionamiento exacto de la pila es el siguiente: cuando se ejecuta una
- instrucción PUSH se decrementa SP en dos unidades y se guarda en la dirección
- SS:SP el valor a empujar. De esta forma, SS:SP siempre apunta al último valor
- empujado. Cuando se ejecuta un POP, se recoge el valor de la dirección SS:SP y
- se incrementa SP en dos unidades.
-
- Ambas instrucciones llevan un solo operando, que especifica de donde se toma
- el valor (en el caso de PUSH) o donde se almacena (en el caso de POP). Los
- operandos pueden ser uno cualquiera de los siguientes:
-
- - Un registro de 16 bits de los siguientes: AX, BX, CX, DX, SI, DI, BP o SP.
- - Un registro de segmento (no se permite POPear CS, porque implicaría un
- salto del programa a otra dirección, y para esto ya hay otras instruccio-
- nes).
- - Una referencia a memoria (con cualquiera de los modos de direccionamiento
- que vimos con la instrucción MOV).
- - Un valor inmediato (por ejemplo, PUSH 55AAh) (sólo para PUSH y en 386 o
- superiores).
-
- Veamos un pequeño listado en ASM y los contenidos de la pila y los registros
- con cada instrucción. El listado es el siguiente:
-
- MOV AX,8000h
- MOV SS,AX
- MOV SP,0FFFEh ; inicializa la pila
- MOV AX,55AAh
- PUSH AX ; empuja el valor 55AAh
- MOV BX,AX
- ADD BX,1111h ; suma a BX el valor 1111h
- PUSH BX
- POP AX
- POP BX
-
- Estudiemos lo que ocurre a cada paso:
-
- * MOV AX,8000h
- * MOV SS,AX
- * MOV SP,0FFFEh ; inicializa la pila
-
- Estas instrucciones inicializan SS y SP para situar la pila al final del
- segmento que comienza en la dirección absoluta 80000h. En este caso, no nos
- interesan los contenidos anteriores de esa zona de memoria. Representaremos el
- contenido de la pila como sigue:
-
- Dirección de memoria Valor Registros del uP
- -------------------------------------- ------------ ------------------
- Segmento Offset Dirección absoluta Valor SS = 8000h
- -------- ------ ------------------ ----- SP = 0FFFEh
- 8000h 0FFFFh 8FFFFh .............. ??
- 8000h 0FFFEh 8FFFEh .............. ??
- 8000h 0FFFDh 8FFFDh .............. ??
- 8000h 0FFFCh 8FFFCh .............. ??
- 8000h 0FFFBh 8FFFBh .............. ??
- 8000h 0FFFAh 8FFFAh .............. ??
-
- * MOV AX,55AAh
-
- Después de esta instrucción, el registro AX contendrá 55AAh.
-
- * PUSH AX ; empuja el valor 55AAh
-
- Nada más ejecutarla, así quedarán la pila y los registros del micro:
-
- Dirección de memoria Valor Registros del uP
- -------------------------------------- ------------ ------------------
- Segmento Offset Dirección absoluta Valor SS = 8000h
- -------- ------ ------------------ ----- SP = 0FFFCh
- 8000h 0FFFFh 8FFFFh .............. ?? AX = 55AAh
- 8000h 0FFFEh 8FFFEh .............. ??
- 8000h 0FFFDh 8FFFDh .............. 55h
- 8000h 0FFFCh 8FFFCh .............. AAh
- 8000h 0FFFBh 8FFFBh .............. ??
- 8000h 0FFFAh 8FFFAh .............. ??
-
- * MOV BX,AX
- * ADD BX,1111h ; suma a BX el valor 1111h
- * PUSH BX
-
- Ya que 55AAh + 1111h = 66BBh, así quedarán las cosas:
-
- Dirección de memoria Valor Registros del uP
- -------------------------------------- ------------ ------------------
- Segmento Offset Dirección absoluta Valor SS = 8000h
- -------- ------ ------------------ ----- SP = 0FFFAh
- 8000h 0FFFFh 8FFFFh .............. ?? AX = 55AAh
- 8000h 0FFFEh 8FFFEh .............. ?? BX = 66BBh
- 8000h 0FFFDh 8FFFDh .............. 55h
- 8000h 0FFFCh 8FFFCh .............. AAh
- 8000h 0FFFBh 8FFFBh .............. 66h
- 8000h 0FFFAh 8FFFAh .............. BBh
-
- * POP AX
-
- Se tomará el valor de SS:SP, se guardará en AX, y se sumará dos a SP:
-
- Dirección de memoria Valor Registros del uP
- -------------------------------------- ------------ ------------------
- Segmento Offset Dirección absoluta Valor SS = 8000h
- -------- ------ ------------------ ----- SP = 0FFFCh
- 8000h 0FFFFh 8FFFFh .............. ?? AX = 66BBh
- 8000h 0FFFEh 8FFFEh .............. ?? BX = 66BBh
- 8000h 0FFFDh 8FFFDh .............. 55h
- 8000h 0FFFCh 8FFFCh .............. AAh
- 8000h 0FFFBh 8FFFBh .............. 66h
- 8000h 0FFFAh 8FFFAh .............. BBh
-
- * POP BX
-
- Análogo a la instrucción anterior, pero con BX:
-
- Dirección de memoria Valor Registros del uP
- -------------------------------------- ------------ ------------------
- Segmento Offset Dirección absoluta Valor SS = 8000h
- -------- ------ ------------------ ----- SP = 0FFFEh
- 8000h 0FFFFh 8FFFFh .............. ?? AX = 66BBh
- 8000h 0FFFEh 8FFFEh .............. ?? BX = 55AAh
- 8000h 0FFFDh 8FFFDh .............. 55h
- 8000h 0FFFCh 8FFFCh .............. AAh
- 8000h 0FFFBh 8FFFBh .............. 66h
- 8000h 0FFFAh 8FFFAh .............. BBh
-
- Os habréis dado cuenta de que podríamos haber inicializado SP a 0 para apro-
- vechar también los dos últimos bytes del segmento. En realidad, esto es algo
- de lo que no es necesario preocuparse prácticamente nunca, pues el DOS se en-
- carga de inicializar la pila generalmente.
-
- Uno de los usos más habituales de la pila es el de preservar el contenido de
- uo o varios registros que van a ser utilizados en un fragmento de código. Suele
- ser algo así:
-
- PUSH AX ; Guardamos AX, BX y CX
- PUSH BX
- PUSH CX
- ; [...] Código que modifica AX, BX y CX
- POP CX ; Recuperamos AX, BX y CX
- POP BX
- POP CX
-
- Ahora ya estamos preparados para introducir un importante aspecto de la
- programación en ASM: las interrupciones. Conociendo las interrupciones, ya ten-
- dremos el bagaje necesario para acometer la escritura de programas usando
- ensambladores comerciales como el TASM o el MASM, comenzando con el ya tradi-
- cional 'Hello, world'.
-
- Salut :-)
-
- Jon